// 
// SoapTrace.Net - A soap message monitoring utility. 
// 
// Copyright (C) 2004 Matthew Ward
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
// 
// Matthew Ward (mrward@users.sourceforge.net)
// 

using SoapTrace.Core.Communication;
using System;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace SoapTrace.Core.Formatters
{
	/// <summary>
	/// Soap message formatter that does not do any special text formatting
	/// based on the actual web service. 
	/// </summary>
	/// <remarks>
	/// <para>This formatter will only do the following formatting:</para>
	/// <list type="bullet">
	///		<item>
	///			<term>Show or hide the Http message header.</term>
	///		</item>
	///		<item>
	///			<term>Show the Http message body with no formatting.</term>
	///		</item>
	///		<item>
	///			<term>Show the Http message body with standard indenting of
	///			the soap message xml.
	///			</term>
	///		</item>
	///		<item>
	///			<term>Show the parameters and the return values of the soap
	///			message with no surrounding soap envelope.
	///			</term>
	///		</item>
	/// 	<item>
	/// 		<term>Show or hide the soap envelope header.
	/// 		</term>
	/// 	</item>
	/// </list>
	/// </remarks>
	public class SimpleMessageFormatter : IMessageFormatter
	{
		/// <summary>
		/// Flag to indicate whether to show soap message headers.
		/// </summary>
		private bool m_ShowSoapHeaders;

		/// <summary>
		/// Flag to indicate whether to display the soap message parameters only.
		/// </summary>
		/// <remarks>
		/// This flag does not work well with complex types since it assumes
		/// that all message parameters are simple types (e.g. string, int...).
		/// </remarks>
		private bool m_ShowSoapMessageParameters;

		/// <summary>
		/// Flag to indicate whether to 'pretty print' the soap message.
		/// </summary>
		private bool m_FormatSoapMessage;
		
		/// <summary>
		/// Flag to indicate whether to remove the soap message envelope.
		/// </summary>
		private bool m_ShowSoapEnvelope = true;
		
		/// <summary>
		/// The soap namespace.
		/// </summary>
		public const string SoapNamespace = "http://schemas.xmlsoap.org/soap/envelope/";

		/// <summary>
		/// The xml namespace declaration string.
		/// </summary>
		public const string XmlNamespaceDeclaration = "xmlns";
		
		private XmlFormatter m_XmlFormatter = new XmlFormatter( );
		
		private HttpHeaderFormatter m_HttpHeaderFormatter = new HttpHeaderFormatter( );
		
		/// <summary>
		/// Creates a new instance of the <see cref="SimpleMessageFormatter"/>
		/// </summary>
		public SimpleMessageFormatter( )
		{
		}

		/// <summary>
		/// Get or sets the flag indicating whether or not to show
		/// the soap message headers.
		/// </summary>
		public bool ShowSoapHeaders
		{
			[ DebuggerStepThrough ]
			get
			{
				return m_ShowSoapHeaders;
			}

			[ DebuggerStepThrough ]
			set
			{
				m_ShowSoapHeaders = value;
			}
		}

		/// <summary>
		/// Gets or sets the flag that indicates whether the soap 
		/// parameters are extracted from soap message and displayed.
		/// </summary>
		public bool ShowSoapMessageParameters
		{
			[ DebuggerStepThrough ]
			get
			{
				return m_ShowSoapMessageParameters;
			}

			[ DebuggerStepThrough ]
			set
			{
				m_ShowSoapMessageParameters = value;
			}
		}

		/// <summary>
		/// Gets or sets a flag indicating whether to format the 
		/// soap message xml.  This does a "pretty print" of the message.
		/// </summary>
		public bool FormatSoapMessage
		{
			[ DebuggerStepThrough ]
			get
			{
				return m_FormatSoapMessage;
			}

			[ DebuggerStepThrough ]
			set
			{
				m_FormatSoapMessage = value;
			}
		}
		
		/// <summary>
		/// Gets or sets a flag indicating whether to show the
		/// soap envelope.
		/// </summary>
		public bool ShowSoapEnvelope
		{
			[ DebuggerStepThrough ]
			get
			{
				return m_ShowSoapEnvelope;
			}

			[ DebuggerStepThrough ]
			set
			{
				m_ShowSoapEnvelope = value;
			}		
		}
		
		/// <summary>
		/// Removes the soap message envelope.
		/// </summary>
		/// <param name="message">The soap message.</param>
		/// <returns>The soap message body.</returns>
		public static string RemoveSoapEnvelope( string message )
		{
			string StrippedMessage = String.Empty;
			
			string NamespacePrefix = GetSoapNamespacePrefix( message );
			
			// Remove text up to the end of the <soap:Body> element. 
			string RegularExpression = String.Concat( "[^$]*<", NamespacePrefix, ":Body>" );
			StrippedMessage = Regex.Replace( message, RegularExpression, "", RegexOptions.Multiline );
			
			// Remove text from the </soap:Body> element to the end of the message.
			RegularExpression = String.Concat( "</", NamespacePrefix, ":Body>[^$]*" );
			StrippedMessage = Regex.Replace( StrippedMessage, RegularExpression, "", RegexOptions.Multiline );
			
			// Remove newlines from the beginning and end of the text.
			StrippedMessage = StrippedMessage.Trim( '\t', '\r', '\n' );

			return StrippedMessage;
		}	
		
		/// <summary>
		/// Gets the namespace prefix used in the soap message.
		/// </summary>
		/// <param name="message">A soap message.</param>
		/// <returns>The soap namespace prefix.</returns>
		public static string GetSoapNamespacePrefix( string message )
		{
			string Namespace = "soap";
	
			// Match xml namespace declaration.
			Match Match = Regex.Match( message, String.Concat( XmlNamespaceDeclaration, ":.*?", SoapNamespace ) );
			
			if( Match.Success )
			{
				// Look for last occurrence of ":prefix=".
				
				int PrefixStartIndex = Match.Value.LastIndexOf( String.Concat( XmlNamespaceDeclaration, ":" ) );
								
				if( PrefixStartIndex != -1 )
				{					
					int PrefixEndIndex = Match.Value.IndexOf( '=', PrefixStartIndex );
					
					if( PrefixEndIndex != -1 )
					{
						int NamespaceStartIndex = PrefixStartIndex + XmlNamespaceDeclaration.Length + 1;
						
						Namespace = Match.Value.Substring( NamespaceStartIndex, 
						                                  PrefixEndIndex - NamespaceStartIndex );
					}
				}				
			}
			
			return Namespace;
		}
	
		#region IMessageFormatter Members

		/// <summary>
		/// Formats a soap message request.
		/// </summary>
		/// <param name="server">The host name of the server
		/// or the IP address.</param>
		/// <param name="uri">The uri of the request.</param>
		/// <param name="webServiceName">The name of the web service.</param>
		/// <param name="webServiceOperation">The name of the web
		/// service operation.</param>
		/// <param name="message">The soap message.</param>
		/// <returns>A formatted soap request.</returns>
		public FormattedText FormatRequest( string server,
			string uri,
			string webServiceName, 
			string webServiceOperation, 
			ISoapMessage message )
		{
			return FormatMessage( message );
		}

		/// <summary>
		/// Formats the soap response message.
		/// </summary>
		/// <param name="server">The host name of the server
		/// or the IP address.</param>
		/// <param name="uri">The uri of the response.</param>
		/// <param name="webServiceName">The name of the web service.</param>
		/// <param name="webServiceOperation">The name of the web service
		/// operation</param>
		/// <param name="message">The soap message itself.</param>
		/// <returns>A formatted message.</returns>
		public FormattedText FormatResponse( string server,
			string uri,
			string webServiceName,
			string webServiceOperation, 
			ISoapMessage message )
		{
			return FormatMessage( message );
		}		
		
		/// <summary>
		/// The name of the message formatter.
		/// </summary>
		public string Name
		{
			[ DebuggerStepThrough ]
			get
			{
				return "Simple Message Formatter";
			}
		}

		/// <summary>
		/// Determines whether this message formatter is configurable.
		/// </summary>
		public bool IsConfigurable
		{
			[ DebuggerStepThrough ]
			get
			{
				return false;
			}
		}

		/// <summary>
		/// Returns the configuration editor for the message formatter.
		/// </summary>
		/// <remarks>
		/// The simple message formatter does not support configuration.
		/// </remarks>
		public IConfigurationEditor ConfigurationEditor
		{
			[ DebuggerStepThrough ]
			get
			{
				return null;
			}
		}

		#endregion

		/// <summary>
		/// Removes the soap message envelope and formats the soap message 
		/// parameters and return values.
		/// </summary>
		/// <param name="message">The soap message string.</param>
		/// <returns>A formatted string that contains just
		/// the parameters without any soap envelope.</returns>
		private static string FormatSoapMessageParameters( string message )
		{
			StringBuilder Text = new StringBuilder( );

			// Load the soap message.

			XmlDocument Document = new XmlDocument( );
			Document.LoadXml( message );

			XmlNamespaceManager NamespaceManager = 
				new XmlNamespaceManager( Document.NameTable );

			NamespaceManager.AddNamespace( "soap", 
				"http://schemas.xmlsoap.org/soap/envelope/" );

			// Locate the soap body element.

			XmlElement SoapBody = 
				( XmlElement )Document.SelectSingleNode( "//soap:Body", 
					NamespaceManager );

			// Format the web service operation node.

			foreach( XmlNode WebServiceOperationNode in SoapBody.ChildNodes )
			{
				Text.Append( FormatWebServiceOperationNode( WebServiceOperationNode ) );
			}

			return Text.ToString( );
		}

		/// <summary>
		/// Formats the soap message.
		/// </summary>
		/// <param name="message">The soap message to format.</param>
		/// <returns>A formatted soap message string.</returns>
		private FormattedText FormatMessage( ISoapMessage message )
		{
			FormattedText FormattedText = new FormattedText( Color.Black );

			// Show headers?

			if( m_ShowSoapHeaders )
			{
				FormattedText.Append( m_HttpHeaderFormatter.Format( message.Header ) );
			}
			
			// Format the xml?

			if( m_ShowSoapMessageParameters )
			{
				FormattedText.Append( FormatSoapMessageParameters( message.Message ) );
			}
			else 
			{
				string MessageText = String.Empty;
				
				if( m_ShowSoapEnvelope )
				{
					MessageText = message.Message;
				}
				else
				{
					MessageText = RemoveSoapEnvelope( message.Message );
				}

				if( m_FormatSoapMessage )
				{
					FormattedText.Append( m_XmlFormatter.Format( IndentedFormat( MessageText ) ) );					
				}
				else
				{
					FormattedText.Append( m_XmlFormatter.Format( MessageText ) );
				}
			}
			
			return FormattedText;
		}
		
		/// <summary>
		/// Formats the web service operation element contained inside the
		/// body of a soap message.
		/// </summary>
		/// <param name="operationNode">The xml node that
		/// represents the web service call.</param>
		/// <returns>A formatted string.</returns>
		private static string FormatWebServiceOperationNode( XmlNode operationNode )
		{
			StringBuilder Text = new StringBuilder( );

			// Add the web service operation name.

			Text.Append( String.Concat( operationNode.Name, "\r\n\r\n" ) );

			foreach( XmlNode ParameterNode in operationNode.ChildNodes )
			{
				Text.Append( IndentedFormat( RemoveNamespace( ParameterNode.OuterXml ) ) );
				Text.Append( "\r\n\r\n" );
			}

			return Text.ToString( );
		}
		
		/// <summary>
		/// Pretty prints the xml.
		/// </summary>
		/// <param name="xml">The xml to format.</param>
		/// <returns>Formatted xml.</returns>
		private static string IndentedFormat( string xml )
		{
			string IdentedXml = xml;
			
			try
			{
				IdentedXml = ValueFormatter.IndentedFormat( xml );
			}
			catch( XmlException )
			{
				// Ignore.
			}
			
			return IdentedXml;
		}
		
		/// <summary>
		/// Removes the namespace declaration from an xml string.
		/// </summary>
		/// <param name="xml">The xml with a namespace.</param>
		/// <returns>Xml with namespace removed.</returns>
		private static string RemoveNamespace( string xml )
		{
			string StrippedXml = Regex.Replace( xml, "xmlns=\".*?\"", "" );
			StrippedXml = Regex.Replace( StrippedXml, "xsi:type=\".*?\"", "" );
                          
			return Regex.Replace( StrippedXml, "xmlns:xsi=\".*?\"", "" );
		}
	}
}
